package conn

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"github.com/go-redis/redis/v8"
	_ "github.com/go-sql-driver/mysql"
	"github.com/ip2location/ip2location-go/v9"
	"github.com/jmoiron/sqlx"
	"golang.org/x/crypto/chacha20poly1305"
	"log"
	"time"
)

var (
	xxteaKey = ""
	ctx      = context.Background()
)

func Use(xxKey string) {
	xxteaKey = xxKey
}

func Chacha20Encode(msg, pass string) string {

	key := sha256.Sum256([]byte(pass))
	aead, _ := chacha20poly1305.NewX(key[:])
	nonce := make([]byte, chacha20poly1305.NonceSizeX)

	return base64.StdEncoding.EncodeToString(aead.Seal(nil, nonce, []byte(msg), nil))
}

func Chacha20Decode(ciphertext, pass string) ([]byte, error) {

	decode, _ := base64.StdEncoding.DecodeString(ciphertext)
	key := sha256.Sum256([]byte(pass))
	aead, _ := chacha20poly1305.NewX(key[:])
	nonce := make([]byte, chacha20poly1305.NonceSizeX)

	return aead.Open(nil, nonce, decode, nil)
}

func InitDB(dsn string, maxIdleConn, maxOpenConn int) *sqlx.DB {

	var (
		dst = []byte(dsn)
		err error
	)
	if xxteaKey != "" {
		dst, err = Chacha20Decode(dsn, xxteaKey)
		if err != nil {

			log.Fatalln(err)
		}
	}

	db, err := sqlx.Connect("mysql", string(dst))
	if err != nil {
		log.Fatalln(err)
	}

	db.SetMaxOpenConns(maxOpenConn)
	db.SetMaxIdleConns(maxIdleConn)
	db.SetConnMaxLifetime(time.Second * 30)
	err = db.Ping()
	if err != nil {
		log.Fatalln(err)
	}

	return db
}

func InitRedisSentinel(dsn []string, psd, name string, db int) *redis.Client {

	var (
		dst = []byte(psd)
		err error
	)
	if xxteaKey != "" {
		dst, err = Chacha20Decode(psd, xxteaKey)
		if err != nil {

			log.Fatalln(err)
		}
	}

	reddb := redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:    name,
		SentinelAddrs: dsn,
		Password:      string(dst), // no password set
		DB:            db,          // use default DB
		DialTimeout:   10 * time.Second,
		ReadTimeout:   30 * time.Second,
		WriteTimeout:  30 * time.Second,
		PoolSize:      500,
		PoolTimeout:   30 * time.Second,
		MaxRetries:    2,
		IdleTimeout:   5 * time.Minute,
	})
	_, err = reddb.Ping(ctx).Result()
	if err != nil {
		log.Fatalf("InitRedisSentinel failed: %s", err.Error())
	}

	return reddb
}

func InitRedis(dsn string, psd string, db int) *redis.Client {

	var (
		dst = []byte(psd)
		err error
	)
	if xxteaKey != "" {
		dst, err = Chacha20Decode(psd, xxteaKey)
		if err != nil {
			log.Fatalln("InitRedis", err)
		}
	}

	reddb := redis.NewClient(&redis.Options{
		Addr:         dsn,
		Password:     string(dst), // no password set
		DB:           db,          // use default DB
		DialTimeout:  5 * time.Second,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		PoolSize:     300,
		PoolTimeout:  15 * time.Second,
		MaxRetries:   3,
		IdleTimeout:  3 * time.Minute,
	})
	_, err = reddb.Ping(ctx).Result()
	if err != nil {
		log.Fatalf("InitRedis failed: %s", err.Error())
	}

	return reddb
}

func InitRedisSentinelRead(dsn []string, psd, name string, db int) *redis.ClusterClient {

	var (
		dst = []byte(psd)
		err error
	)
	if xxteaKey != "" {
		dst, err = Chacha20Decode(psd, xxteaKey)
		if err != nil {
			log.Fatalln("InitRedisSentinelRead", err)
		}
	}

	reddb := redis.NewFailoverClusterClient(&redis.FailoverOptions{
		MasterName:     name,
		SentinelAddrs:  dsn,
		Password:       string(dst), // no password set
		DB:             db,          // use default DB
		DialTimeout:    10 * time.Second,
		ReadTimeout:    30 * time.Second,
		WriteTimeout:   30 * time.Second,
		PoolSize:       500,
		PoolTimeout:    30 * time.Second,
		MaxRetries:     2,
		IdleTimeout:    5 * time.Minute,
		SlaveOnly:      true,
		RouteByLatency: true,
	})
	_, err = reddb.Ping(ctx).Result()
	if err != nil {
		log.Fatalf("InitRedisSentinelRead failed: %s", err.Error())
	}

	return reddb
}

func InitRedisCluster(dsn []string, psd string) *redis.ClusterClient {

	var (
		dst = []byte(psd)
		err error
	)
	if xxteaKey != "" {
		dst, err = Chacha20Decode(psd, xxteaKey)
		if err != nil {
			log.Fatalln("InitRedisCluster", err)
		}
	}

	reddb := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs:        dsn,
		Password:     string(dst), // no password set
		DialTimeout:  10 * time.Second,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		PoolSize:     500,
		PoolTimeout:  30 * time.Second,
		MaxRetries:   2,
		IdleTimeout:  5 * time.Minute,
	})

	_, err = reddb.Ping(ctx).Result()
	if err != nil {
		log.Fatalf("initRedisSlave failed: %s", err.Error())
	}

	return reddb
}

func InitIpDB(path string) *ip2location.DB {

	db, err := ip2location.OpenDB(path)
	if err != nil {
		log.Fatalf("initIPBin failed: %s", err.Error())
	}

	return db
}
